"""
认证工具函数模块
提供密码处理、令牌生成等工具函数
"""
import secrets
import string
import hashlib
import time
from datetime import datetime, timedelta
from typing import Optional, Dict, Any

import bcrypt
import jwt
from flask import current_app, request
from flask_login import current_user
from flask_jwt_extended import create_access_token, create_refresh_token, get_jwt_identity

from app.models.user import User
from app.auth.exceptions import AuthenticationError, InvalidTokenError, TokenExpiredError

# 简单的内存存储（生产环境应使用Redis）
_verification_codes = {}
_reset_tokens = {}

def hash_password(password: str) -> str:
    """
    使用bcrypt哈希密码
    
    Args:
        password: 明文密码
        
    Returns:
        str: 哈希后的密码
    """
    if not password:
        raise ValueError("密码不能为空")
    
    # 生成盐并哈希密码
    salt = bcrypt.gensalt(rounds=current_app.config.get('BCRYPT_LOG_ROUNDS', 12))
    hashed = bcrypt.hashpw(password.encode('utf-8'), salt)
    return hashed.decode('utf-8')

def verify_password(password: str, hashed: str) -> bool:
    """
    验证密码是否匹配
    
    Args:
        password: 明文密码
        hashed: 哈希后的密码
        
    Returns:
        bool: 是否匹配
    """
    if not password or not hashed:
        return False
    
    try:
        return bcrypt.checkpw(password.encode('utf-8'), hashed.encode('utf-8'))
    except (ValueError, TypeError):
        return False

def generate_jwt_tokens(user: User, remember_me: bool = False) -> Dict[str, str]:
    """
    生成JWT访问令牌和刷新令牌
    
    Args:
        user: 用户对象
        remember_me: 是否记住登录
        
    Returns:
        dict: 包含access_token和refresh_token的字典
    """
    if not user or not user.is_active:
        raise AuthenticationError("用户不存在或已被禁用")
    
    # 额外的令牌声明
    additional_claims = {
        'user_id': user.id,
        'username': user.username,
        'role': user.role,
        'is_admin': user.is_admin
    }
    
    # 创建访问令牌
    access_token = create_access_token(
        identity=user.id,
        additional_claims=additional_claims,
        expires_delta=current_app.config['JWT_ACCESS_TOKEN_EXPIRES']
    )
    
    # 创建刷新令牌（如果记住登录则延长有效期）
    refresh_expires = current_app.config['JWT_REFRESH_TOKEN_EXPIRES']
    if remember_me:
        refresh_expires = timedelta(days=60)  # 记住登录时延长到60天
    
    refresh_token = create_refresh_token(
        identity=user.id,
        additional_claims=additional_claims,
        expires_delta=refresh_expires
    )
    
    return {
        'access_token': access_token,
        'refresh_token': refresh_token,
        'token_type': 'bearer',
        'expires_in': int(current_app.config['JWT_ACCESS_TOKEN_EXPIRES'].total_seconds())
    }

def verify_jwt_token(token: str) -> Dict[str, Any]:
    """
    验证JWT令牌
    
    Args:
        token: JWT令牌
        
    Returns:
        dict: 令牌中的声明
        
    Raises:
        InvalidTokenError: 令牌无效
        TokenExpiredError: 令牌已过期
    """
    try:
        secret_key = current_app.config['JWT_SECRET_KEY']
        algorithm = current_app.config.get('JWT_ALGORITHM', 'HS256')
        
        payload = jwt.decode(token, secret_key, algorithms=[algorithm])
        return payload
    except jwt.ExpiredSignatureError:
        raise TokenExpiredError("令牌已过期")
    except jwt.InvalidTokenError:
        raise InvalidTokenError("无效的令牌")

def generate_reset_token(user_id: int, expires_in: int = 3600) -> str:
    """
    生成密码重置令牌
    
    Args:
        user_id: 用户ID
        expires_in: 令牌有效期（秒）
        
    Returns:
        str: 重置令牌
    """
    # 生成随机令牌
    token = secrets.token_urlsafe(32)
    
    # 存储令牌信息
    _reset_tokens[token] = {
        'user_id': user_id,
        'expires_at': time.time() + expires_in,
        'used': False
    }
    
    return token

def verify_reset_token(token: str) -> Optional[int]:
    """
    验证密码重置令牌
    
    Args:
        token: 重置令牌
        
    Returns:
        int or None: 用户ID（如果令牌有效）
    """
    token_data = _reset_tokens.get(token)
    
    if not token_data:
        return None
    
    # 检查令牌是否已使用或过期
    if token_data['used'] or time.time() > token_data['expires_at']:
        # 清理过期或已使用的令牌
        _reset_tokens.pop(token, None)
        return None
    
    return token_data['user_id']

def mark_reset_token_used(token: str) -> None:
    """
    标记重置令牌为已使用
    
    Args:
        token: 重置令牌
    """
    if token in _reset_tokens:
        _reset_tokens[token]['used'] = True

def generate_verification_code(length: int = 6, expires_in: int = 600) -> str:
    """
    生成验证码
    
    Args:
        length: 验证码长度
        expires_in: 有效期（秒）
        
    Returns:
        str: 验证码
    """
    # 生成数字验证码
    code = ''.join(secrets.choice(string.digits) for _ in range(length))
    
    # 存储验证码信息
    _verification_codes[code] = {
        'created_at': time.time(),
        'expires_at': time.time() + expires_in,
        'used': False
    }
    
    return code

def validate_verification_code(code: str) -> bool:
    """
    验证验证码
    
    Args:
        code: 验证码
        
    Returns:
        bool: 是否有效
    """
    code_data = _verification_codes.get(code)
    
    if not code_data:
        return False
    
    # 检查验证码是否已使用或过期
    if code_data['used'] or time.time() > code_data['expires_at']:
        # 清理过期或已使用的验证码
        _verification_codes.pop(code, None)
        return False
    
    return True

def mark_verification_code_used(code: str) -> None:
    """
    标记验证码为已使用
    
    Args:
        code: 验证码
    """
    if code in _verification_codes:
        _verification_codes[code]['used'] = True

def get_current_user() -> Optional[User]:
    """
    获取当前用户
    
    Returns:
        User or None: 当前用户对象
    """
    # 首先检查Flask-Login的当前用户
    if current_user.is_authenticated:
        return current_user
    
    # 然后检查JWT令牌
    try:
        from flask_jwt_extended import get_jwt_identity
        user_id = get_jwt_identity()
        if user_id:
            return User.query.get(user_id)
    except Exception:
        pass
    
    return None

def check_permission(user: User, permission: str) -> bool:
    """
    检查用户是否具有指定权限
    
    Args:
        user: 用户对象
        permission: 权限字符串
        
    Returns:
        bool: 是否具有权限
    """
    if not user or not user.is_active:
        return False
    
    # 管理员拥有所有权限
    if user.is_admin:
        return True
    
    # 根据角色检查权限
    role_permissions = {
        'user': ['read', 'write_own', 'comment'],
        'moderator': ['read', 'write_own', 'comment', 'moderate'],
        'admin': ['read', 'write_own', 'comment', 'moderate', 'administer']
    }
    
    user_permissions = role_permissions.get(user.role, ['read'])
    return permission in user_permissions

def generate_api_key() -> str:
    """
    生成API密钥
    
    Returns:
        str: API密钥
    """
    return secrets.token_urlsafe(32)

def generate_user_session_id() -> str:
    """
    生成用户会话ID
    
    Returns:
        str: 会话ID
    """
    return secrets.token_urlsafe(16)

def get_client_ip() -> str:
    """
    获取客户端IP地址
    
    Returns:
        str: IP地址
    """
    if request.headers.get('X-Forwarded-For'):
        return request.headers.get('X-Forwarded-For').split(',')[0].strip()
    elif request.headers.get('X-Real-IP'):
        return request.headers.get('X-Real-IP')
    else:
        return request.remote_addr

def get_user_agent() -> str:
    """
    获取用户代理字符串
    
    Returns:
        str: 用户代理
    """
    return request.headers.get('User-Agent', '')

def generate_fingerprint() -> str:
    """
    生成客户端指纹（用于识别设备）
    
    Returns:
        str: 指纹哈希
    """
    ip = get_client_ip()
    user_agent = get_user_agent()
    accept_language = request.headers.get('Accept-Language', '')
    accept_encoding = request.headers.get('Accept-Encoding', '')
    
    fingerprint_data = f"{ip}:{user_agent}:{accept_language}:{accept_encoding}"
    return hashlib.sha256(fingerprint_data.encode()).hexdigest()

def validate_password_strength(password: str) -> tuple[bool, str]:
    """
    验证密码强度
    
    Args:
        password: 密码
        
    Returns:
        tuple: (是否有效, 错误消息)
    """
    if len(password) < 8:
        return False, "密码长度至少8个字符"
    
    if len(password) > 128:
        return False, "密码长度不能超过128个字符"
    
    # 检查字符类型
    has_upper = any(c.isupper() for c in password)
    has_lower = any(c.islower() for c in password)
    has_digit = any(c.isdigit() for c in password)
    has_special = any(not c.isalnum() for c in password)
    
    # 至少包含三种字符类型
    character_types = sum([has_upper, has_lower, has_digit, has_special])
    if character_types < 3:
        return False, "密码应包含大小写字母、数字和特殊字符中的至少三种"
    
    # 检查常见弱密码
    weak_passwords = ['123456', 'password', 'qwerty', '111111', 'admin']
    if password.lower() in weak_passwords:
        return False, "密码过于简单，请使用更复杂的密码"
    
    return True, "密码强度符合要求"

def cleanup_expired_tokens():
    """
    清理过期的令牌和验证码
    应在定时任务中调用
    """
    current_time = time.time()
    
    # 清理过期重置令牌
    expired_tokens = [
        token for token, data in _reset_tokens.items()
        if current_time > data['expires_at']
    ]
    for token in expired_tokens:
        _reset_tokens.pop(token, None)
    
    # 清理过期验证码
    expired_codes = [
        code for code, data in _verification_codes.items()
        if current_time > data['expires_at']
    ]
    for code in expired_codes:
        _verification_codes.pop(code, None)

def log_auth_attempt(user_id: Optional[int], success: bool, action: str, details: str = ""):
    """
    记录认证尝试
    
    Args:
        user_id: 用户ID（可选）
        success: 是否成功
        action: 操作类型（login, register, reset_password等）
        details: 详细信息
    """
    from app.utils.logging import get_logger
    logger = get_logger('auth')
    
    log_data = {
        'user_id': user_id,
        'success': success,
        'action': action,
        'ip': get_client_ip(),
        'user_agent': get_user_agent(),
        'timestamp': datetime.utcnow().isoformat(),
        'details': details
    }
    
    if success:
        logger.info(f"认证成功: {action}", extra=log_data)
    else:
        logger.warning(f"认证失败: {action}", extra=log_data)